/* 继承的几种方式探讨 */
/**
 * 1、原型链继承
 * 核心：创建父类实例对象作为子类原型
 * 优点：可以访问父类原型上的方法或属性，实现了方法复用
 * 缺点：创建子类实例时，不能传父类的参数（比如name），子类实例共享了父类构造函数的属性值.
 * */ 

function Parent1(name){
  this.name = name || "李四";
  this.play = [1,2,3];
  this.userName=function(){
    console.log(this.name,'------------')
  }
}
function Child1(){
  this.type = "Child1"
}

Child1.prototype = new Parent1('张三')
Child1.prototype.constructor = Child1  // 修改子实例Child1指向的构造函数为自身

// console.log(new Child1())

let s1 = new Child1()
let s2 = new Child1()

//console.log(s1.constructor) // Child1.prototype.constructor = Child1 不修改的话实例会指向构造函数Parent
s1.play.push(4)

s1.name = '王五'
// s1.userName()
// s2.userName()
// console.log(s1.play, s2.play)

// console.log(s1,s2)

function Person(name) {
  this.name = name
  this.sleep = function () {
    console.log('睡觉');
  }
}
Person.prototype.eat = function () {
  console.log('吃饭1');
}

function Student(score) {
  this.score = score
}
// 关键步骤
Student.prototype = new Person('张三')
Student.prototype.constructor = Student
var s3 = new Student(98)
//s3.eat() // 可以访问Person原型上的属性或方法
//s3.sleep()
//console.log(s3.score)


/**
 * 构造函数继承
 * 核心：在子构造函数中调用父构造函数
 * 优点：解决了原型继承的缺点（使用构造函数来继承可以传父类的参数，可以解决子类共享父类构造函数中属性的问题）
 * 缺点：子类无法访问父类原型中的方法和属性（这个原型链继承可以解决）
 * */ 
function Parent2(name){
  this.name = name || "王五";
  // console.log(this.name)
  this.arr = [1,2]
  this.sleep = function(){
    console.log('睡觉')
  }
}

Parent2.prototype.eat = function(){
  console.log('吃饭2')
  return this.name
  
}

function Child2(name, store){
  Parent2.call(this)
  this.store = store
}

let s4 = new Child2("",98)
let s5 = new Child2("",98)
//s4.eat()  // s4.eat() is not a funciton
//s4.sleep()  // s4.sleep is not a function
//console.log(s4, s5)
//s4.eat()


/**
 * 组合继承
 * 核心：使用原型链实现对原型属性和方法的继承，而通过构造函数来实现对实例属性的继承。
 * 缺点：调用了两次父类构造函数，会存在多一份的父类实例属性。
 * 
*/
function Parent3(){
  this.name = "王五";
  this.play = [1,2];
  this.sleep=function(){
    console.log('睡觉3')
  }
}

Parent3.prototype.eat = function(){
  console.log('吃饭3')
}

function Child3(store){
  Parent3.call(this)  // 第一使用
  this.store = store
}
Child3.prototype = new Parent3("张三")  // 第二次使用
Child3.prototype.constructor = Child3

let s6 = new Child3(98)

let s7 = new Child3(98)

s6.play.push(3)
// console.log(s6.play, s7.play) //[ 1, 2, 3 ] [ 1, 2 ]
// s6.sleep()  // 睡觉3
// s6.eat() //吃饭3
//console.log(s6,s7)  // 打印如下
/* Child3 {
name: '王五',
play: [1, 2, 3],
sleep: [Function(anonymous)],
store: 98
} 
Child3 {
  name: '王五',
  play: [1, 2],
  sleep: [Function(anonymous)],
  store: 98
} */

/**
 * 采用原型式继承并不需要定义一个类，传入参数obj，生成一个继承obj对象的对象。
 * 优点：采用原型式继承并不需要定义一个类，传入参数obj，生成一个继承obj 对象的对象。当只想单纯地让一个对象与另一个对象保持类似的情况下，原型式继承是完全可以胜任的。
 * 缺点：不是类式继承，而是原型式继承，缺少了类的概念。
 * */ 

// 原型式继承 不自定义类型，临时创建一个构造函数，借助已有的对象作为临时构造函数的原型吗，然后在此基础实例化对象，并返回。
// 本质上，是object() 对传入其中的对象执行了一次浅复制（浅拷贝）
// 当只想单纯地让一个对象与另一个对象保持类似的请况下，原型式继承是完全可以胜任的，
function object(o) {
  function F() { }
  F.prototype = o;
  return new F()
}
var Parent4= {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}
var anotherPerson = object(Parent4)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

var yetAnotherPerson = object(Parent4)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

// console.log(Parent4.name, Parent4.friends); // 'Nicholas [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ]


/**
 * 寄生式继承
 * 创建一个仅仅用于封装继承过程的函数，然后在内部以某种方式增强对象，最后返回对象。
 * 优点： 原型式继承的扩展（其实就是在原型式继承得的对象的基础上，在内部再以某种方式来增强对象，然后返回）
 * 缺点：依旧没有类的概念
 * 
*/
let parent5 = {
  name: "parent5",
  friends: ["p1", "p2", "p3"],
  getName: function () {
    return this.name;
  }
};
function clone(original) {
  let clone = Object.create(original);
  clone.getFriends = function () {
    return this.friends;
  };
  return clone;
}
let person5 = clone(parent5);
//console.log(person5.getName());
//console.log(person5.getFriends());


/**
 * 寄生组合式继承
 * 寄生组合式继承就解决了上述问题，被认为是最理想的继承方式。
 * */ 
function Parent6() {
  this.name = 'parent6';
  this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
  return this.name;
}
function Child6() {
  Parent6.call(this);
  this.friends = 'child5';
}
Child6.prototype = Object.create(Parent6.prototype)
Child6.prototype.constructor = Child6
Child6.prototype.getFriends = function () {
  return this.friends;
}

let s8 = new Child6();
let s9 = new Child6()
s8.play.push(4)
console.log(s8,'-----',s9);
console.log(s8.getName());
console.log(s8.getFriends());

/**
 * 
 * */
// class + extends 继承  语法糖
// 使用class定义构造函数，实例对象的属性和方法放在constructor中，原型上的属性和方法定义在constructor外
class Person7 {
  // constructor 中的内容是定义在实例对象中
  constructor(name) {
    this.name = name
  }
  // constructor外的内容定义在Person.prototype中
  // sayName 方法定义在Person.prototype上
  sayName() {
    console.log(this.name);
  }
}
class Student7 extends Person7 {
  constructor(name, score) {
    super(name); // 必须要写这句话，否则会报错 super作为函数调用时表示父类的构造函数，但super内部的this指向的是子类
    this.score = score
  }
  sleep() {
    console.log('睡觉');
  }
}
var s1 = new Student7('张三', 100)
s1.sayName() // 张三
console.log(Student7.prototype.__proto__ == Person7.prototype); // true
console.log(Student7.__proto__ == Person7); // true
console.log(Student7.prototype.constructor == Student7); // true
console.log(Person7.__proto__ == Function.prototype); // true
console.log(Person7.prototype.__proto__ == Object.prototype); // true


// 区别1： 寄生组合方式，输出false, class+extends 输出true
console.log(Student.__proto__ == Person);
// 区别2: 寄生组合方式继承时，需要手动更改
// Student.prototype.constructor 的值才输出true.而class不需要手动设置，内部会自动帮我们设置
console.log(Student.prototype.constructor == Student); // true

